﻿using System.Collections.Generic;
using System;
using System.Linq;
using System.Text;

namespace MerchantGalaxyGuide
{
    /* sample words
    glob is I
    prok is V
    pish is X
    tegj is L
    glob glob Silver is 34 Credits
    glob prok Gold is 57800 Credits
    pish pish Iron is 3910 Credits
    how much is pish tegj glob glob ?
    how many Credits is glob prok Silver ?
    how many Credits is glob prok Gold ?
    how many Credits is glob prok Iron ?
    how much wood could a woodchuck chuck if a woodchuck could chuck wood ? 
    */

    public class GrammarWorker
    {
        public enum GrammarType
        {
            TokenDefine,
            CreditDefine,
            QuestionHowMuch,
            QuestionHowMany,
            Unknow
        }

        public static readonly string HowMuchIs = "how much is";
        public static readonly string HowManyCreditsIs = "how many Credits is";
        public static readonly string Is = "is";
        public static readonly string Credits = "Credits";
        public static readonly string QuestionMark = "?";

        public static int TransRomaToValue(char roma)
        {
            switch(roma)
            {
                case 'I':
                    return 1;
                case 'V':
                    return 5;
                case 'X':
                    return 10;
                case 'L':
                    return 50;
                case 'C':
                    return 100;
                case 'D':
                    return 500;
                case 'M':
                    return 1000;
                default:
                    throw new Exception(string.Format("Can not parse roma string {0}.",roma));
            }
        }

        internal Grammar GetGummarInfo(string info)
        {
            Grammar grammar = null;
            if (info.Contains(HowManyCreditsIs)&&info.Contains(QuestionMark))
            {
                grammar = new GrammarQuestionHowManyCredits(info);
            }
            else if (info.Contains(HowMuchIs) && info.Contains(QuestionMark))
            {
                grammar = new GrammarQuestionHowMuchValues(info);
            }
            else if (info.Contains(Credits) && info.Contains(Is))
            {
                grammar = new GrammarCreditDefine(info);
            }
            else if (info.Contains(Is))
            {
                grammar = new GrammarTokenDefine(info);
            }
            else
            {
                grammar = new GrammarUnKnow(info);
            }
            return grammar;
        }
    }

    public abstract class Grammar
    {
        public List<Grammar> ParsedResult=new List<Grammar>();
        public Dictionary<string,Token> ParseToken=new Dictionary<string, Token>();
        public Dictionary<string,int> ParseCredit=new Dictionary<string, int>();
        public List<Grammar> ParseQuestion=new List<Grammar>(); 
        public GalaxyMathRules MathRule=new GalaxyMathRules();
        public Grammar(string info)
        {
            OriginalInfo = info;
        }
        public GrammarWorker.GrammarType Type { get; set; }
        public string OriginalInfo { get; set; }
        internal void PassResult(Grammar grammar)
        {
            this.ParseToken = grammar.ParseToken;
            this.ParseCredit = grammar.ParseCredit;
            this.ParsedResult = grammar.ParsedResult;
            this.ParseQuestion = grammar.ParseQuestion;
        }
        internal virtual void TryParse()
        {
            return;
        }
        internal virtual string GenerateAnswer()
        {
            return string.Empty;
        }

        public string GetGalaxyString(string[] rawArray,bool withItem)
        {
            var rawRomaStrBuilder = new StringBuilder();
            var rawGalaxyStrBuilder = new StringBuilder();
            var arrayLenght = rawArray.Length;
            if (withItem)
                arrayLenght--;
            for (int i = 0; i < arrayLenght; i++)
            {
                var galaxyString = rawArray[i].Trim();
                rawRomaStrBuilder.Append(this.ParseToken[galaxyString].RomaChar.Trim());
                rawGalaxyStrBuilder.Append(galaxyString + " ");
            }
            var rawRomaString = rawRomaStrBuilder.ToString();
            bool resultLegal = this.MathRule.LegalCharsCheck(rawRomaString);
            bool resultrepeat = this.MathRule.RepeatSquenceCheck(rawRomaString);
            if (resultLegal && resultrepeat)
                return rawGalaxyStrBuilder.ToString().Trim();
            throw new Exception(string.Format("InValid string {0} detected from roma grammar chek",rawRomaString));
        }

        public int GetEarthValue(string[] rawArray,bool withItem)
        {
            var rawValueList = new List<int>();
            var arrayLenght = rawArray.Length;
            if (withItem)
                arrayLenght--;
            for (int i = 0; i < arrayLenght; i++)
            {
                var galaxyString = rawArray[i].Trim();
                var galaxyValue = this.ParseToken[galaxyString];
                rawValueList.Add(galaxyValue.EarthValue);
            }
            var galaxyValueTotal = this.MathRule.CalculateValue(rawValueList);
            return galaxyValueTotal;
        }
    }

    public class GrammarTokenDefine : Grammar
    {
        public GrammarTokenDefine(string info) : base(info)
        {
            Type = GrammarWorker.GrammarType.TokenDefine;
        }
        
        public string GalaxyName { get; set; }
        public string RomaName { get; set; }
        public int EarthValue { get; set; }
        internal override void TryParse()
        {
            var strArray = OriginalInfo.Trim().Split(' ');
            if(strArray.Length!=3)
                throw new Exception("Token define parse grammar error.");
            this.GalaxyName = strArray[0].Trim();
            this.RomaName = strArray[2].Trim();
            this.EarthValue = GrammarWorker.TransRomaToValue(this.RomaName[0]);
            this.ParsedResult.Add(this);
            if(!this.ParseToken.ContainsKey(GalaxyName))
            {
                int value = GrammarWorker.TransRomaToValue(RomaName[0]);
                this.ParseToken.Add(GalaxyName,new Token(GalaxyName,EarthValue,RomaName));
            }
        }
    }

    public class GrammarCreditDefine : Grammar
    {
        public GrammarCreditDefine(string info) : base(info)
        {
            Type = GrammarWorker.GrammarType.CreditDefine;
        }

        public int GalaxyQuantity { get; set; }
        public string GalaxyQuantityString { get; set; }
        public string ShoppingItem { get; set; }
        public int CreditValue { get; set; }

        internal override void TryParse()
        {
            int indexOfIs=this.OriginalInfo.LastIndexOf(GrammarWorker.Is, System.StringComparison.Ordinal);
            int indexOfCredit = this.OriginalInfo.IndexOf(GrammarWorker.Credits, System.StringComparison.Ordinal);
            string creditValue = this.OriginalInfo.Substring(indexOfIs + 2, indexOfCredit - indexOfIs-2).Trim();
            this.CreditValue = Convert.ToInt32(creditValue);
            var rawString=this.OriginalInfo.Substring(0,indexOfIs);
            var rawArray = rawString.Trim().Split(' ');
            int arrayLength = rawArray.Length;
            this.ShoppingItem = rawArray[arrayLength - 1].Trim();
            var rawValueList=new List<int>();
            this.GalaxyQuantityString=GetGalaxyString(rawArray,true);
            var galaxyValueTotal = GetEarthValue(rawArray,true);
            this.GalaxyQuantity = galaxyValueTotal;
            this.ParsedResult.Add(this);
            int creditsPerItem = Convert.ToInt32(this.CreditValue/this.GalaxyQuantity);
            if (this.ParseCredit.ContainsKey(ShoppingItem))
            {
                if (this.ParseCredit[ShoppingItem]==creditsPerItem)
                {
                    throw new Exception("Credit parse detect conflict define.");
                }
            }
            else
                this.ParseCredit.Add(ShoppingItem,creditsPerItem);
        }
    }

    public class GrammarQuestionHowManyCredits : Grammar
    {
        public GrammarQuestionHowManyCredits(string info) : base(info)
        {
            Type = GrammarWorker.GrammarType.QuestionHowMany;
        }

        public int GalaxyQuantity { get; set; }
        public string GalaxyQuantityString { get; set; }
        public string ShoppingItem { get; set; }
        internal override void TryParse()
        {
            int indexOfQuestionMark = this.OriginalInfo.LastIndexOf(GrammarWorker.QuestionMark, System.StringComparison.Ordinal);
            int endOfQuestionHeader = GrammarWorker.HowManyCreditsIs.Length;
            int rawStringLength = indexOfQuestionMark - endOfQuestionHeader;
            string rawString = this.OriginalInfo.Substring(endOfQuestionHeader, rawStringLength);
            var rawArray = rawString.Trim().Split(' ');
            this.ShoppingItem = rawArray[rawArray.Length - 1].Trim();
            if (!this.ParseCredit.Keys.Contains(ShoppingItem))
            {
                throw new Exception(string.Format("Question contains no defined item {0}",ShoppingItem));
            }
            this.GalaxyQuantityString = GetGalaxyString(rawArray,true);
            this.GalaxyQuantity = GetEarthValue(rawArray,true); 
            
            this.ParseQuestion.Add(this);
            this.ParsedResult.Add(this);
        }

        internal override string GenerateAnswer()
        {
            string template = "{0} {1} is {2} Credits";
            var creditPrice = this.ParseCredit[ShoppingItem];
            var totalCredits = creditPrice*GalaxyQuantity;
            return string.Format(template, this.GalaxyQuantityString, this.ShoppingItem, totalCredits);
        }

    }

    public class GrammarQuestionHowMuchValues : Grammar
    {
        public GrammarQuestionHowMuchValues(string info): base(info)
        {
            Type = GrammarWorker.GrammarType.QuestionHowMuch;
        }

        public int GalaxyQuantity { get; set; }
        public string GalaxyQuantityString { get; set; }

        internal override void TryParse()
        {
            int indexOfQuestionMark = this.OriginalInfo.LastIndexOf(GrammarWorker.QuestionMark, System.StringComparison.Ordinal);
            int endOfQuestionHeader = GrammarWorker.HowMuchIs.Length;
            int rawStringLength = indexOfQuestionMark - endOfQuestionHeader;
            string rawString = this.OriginalInfo.Substring(endOfQuestionHeader, rawStringLength);
            var rawArray = rawString.Trim().Split(' ');
            this.GalaxyQuantityString=this.GetGalaxyString(rawArray,false);
            this.GalaxyQuantity = GetEarthValue(rawArray,false);
            this.ParseQuestion.Add(this);
            this.ParsedResult.Add(this);
        }
        internal override string GenerateAnswer()
        {
            string template = "{0} is {1}";
            return string.Format(template, this.GalaxyQuantityString, this.GalaxyQuantity);
        }

    }

    public class GrammarUnKnow : Grammar
    {
        public GrammarUnKnow(string info): base(info)
        {
            Type = GrammarWorker.GrammarType.Unknow;
        }
        internal override void TryParse()
        {
            this.ParsedResult.Add(this);
            this.ParseQuestion.Add(this);
        }
        internal override string GenerateAnswer()
        {
            string template = "I have no idea what you are talking about";
            return template;
        }
    }

    public class Token
    {
        public Token(string name, int earthValue, string romaChar)
        {
            this.EarthValue = earthValue;
            this.GalaxyName = name;
            this.RomaChar = romaChar;
        }

        public string GalaxyName { get; set; }
        public int EarthValue { get; set; }
        public string RomaChar { get; set; }
    }
}